package com.errol.generic.http;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;

/**
 * 灵活、安全、可扩展的 HttpService
 * 使用建造者模式构建 http 执行计划，各种请求参数都可以灵活配置
 * 内部捕获输出异常日志，返回请求失败，不会向调用者抛出异常
 * 方便扩展新的请求和响应类型，支持灵活切换第三方 HTTP 客户端工具包
 * <p>
 * 使用 {@link HttpBuilder} 构建 HttpPlan 执行计划，构建过程分为 5 个部分：
 * HttpBuilder：设置请求 url，请求方法，超时时间等参数；由 HttpBuilder.builder() 创建；
 * HttpHeaderBuilder：设置 http header 中的参数；由 httpBuilder.useXxx() 系列方法创建；
 * HttpXxxBodyBuilder：设置 http requestBody 中的参数，不同的请求体编码格式需要对应的 HttpXxxBodyBuilder；由 httpHeaderBuilder.sendXxxData() 系列方法创建；
 * HttpXxxResultBuilder：设置 http responseBody 中的响应类型，不同的响应体编码格式需要对应的 HttpXxxResultBuilder；由 httpXxxBodyBuilder.expectXxxOfYyy() 系列方法创建；
 * HttpPlan：Http 执行计划，定义请求参数和响应参数，由 {@link HttpService} 同步或异步执行；由 httpXxxResultBuilder.build() 方法创建；
 * 除了转构建过程的 useXxx() sendXxxData() expectXxxOfYyy() 系列方法，其他的参数配置方法都是 setXxx() 或 addXxx()；
 * Http 构建执行过程中的异常已经由 {@link HttpLogger} 统一处理，调用者只需根据 {@link HttpResult} 判断请求成功与否；
 * json 序列化与反序列化都使用 ObjectMapper ，且只看 property ，使用 {@link com.fasterxml.jackson.annotation.JsonProperty} 注解映射不同的 javaBean 属性和 jsonStr 属性
 *
 * @author zhang xiao bin
 * @version v1.0
 * @date 2020/09/23
 * @since v
 **/
public final class HttpBuilder {
    /**
     * 请求 URL，由子类静态 create(String url) 方法中调用 super(url); 传递
     */
    private String url;
    /**
     * 请求参数 暂存 map，可能是 URL 携带的参数，或者是 requestBody 中的参数
     */
    private final Map<String, Object> reqParams;
    /**
     * 编码格式，默认 Charset.defaultCharset() 一般是 UTF-8
     */
    private Charset charset;
    /**
     * 连接超时时间，默认 5秒
     */
    private int connectionTimeout;
    /**
     * 读响应超时时间，默认 5秒
     */
    private int readTimeout;
    /**
     * http 执行组件，提供对第三方 HTTP 客户端实现的扩展支持
     */
    private final HttpComponent httpComponent;
    /**
     * http 日志接口，提供对系统日志服务的集成能力
     */
    private final HttpLogger httpLogger;
    /**
     * 捕获的第一个异常，捕获异常后后续的逻辑将会直接跳过，返回请求失败
     */
    private Exception firstException;

    private HttpBuilder(HttpComponent httpComponent, HttpLogger httpLogger, String url) {
        this.httpComponent = httpComponent;
        this.httpLogger = httpLogger;
        this.url = url;
        reqParams = new HashMap<>();
        charset = Charset.defaultCharset();
        connectionTimeout = 5;
        readTimeout = 5;
        firstException = null;
    }

    /**
     * 创建 HttpBuilder ，建议通过 HttpService 实现类进行创建
     *
     * @param httpComponent http 客户端实现，支持使用不同的客户端工具包
     * @param httpLogger    http 日志组件，支持集成系统统一日志服务
     * @param url           请求地址
     */
    public static HttpBuilder builder(HttpComponent httpComponent, HttpLogger httpLogger, String url) {
        Assert.isTrue(httpComponent != null && httpLogger != null && url != null, "构造器参数不能为 null");
        return new HttpBuilder(httpComponent, httpLogger, url);
    }

    /**
     * 添加参数到 reqParams
     *
     * @param key   参数名，key 为 null 或 "" 时抛出异常
     * @param value 参数值，拼接 value 是如果 value 为 null 就转为 ""
     */
    private void addReqParam(String key, Object value) {
        assertKey(key);
        reqParams.put(key, value);
    }

    /**
     * key 为 null 或 "" 时抛出异常
     */
    private void assertKey(String key) {
        Assert.isTrue(!StringUtils.isEmpty(key), "请求参数 key 不能为 null 或 '' ");
    }

    /**
     * value 为 null 时取 "" ，否则调用 toString()
     */
    private String parseValue(Object value) {
        return value == null ? "" : String.valueOf(value);
    }

    /**
     * 拼接 URL 和 reqParams 中的参数
     */
    private void spliceUrlAndReqParams() {
        if (reqParams.size() != 0) {
            String urlParams = spliceReqParams('&', true);
            if (url.endsWith("?")) {
                url += urlParams;
            } else if (url.contains("?")) {
                url += "&" + urlParams;
            } else {
                url += "?" + urlParams;
            }
        }
    }

    /**
     * 将 reqParams 中 key=value 进行拼接，value 如果为 null 则转为 ""
     *
     * @param splicing      多个 key=value 对之间的拼接符
     * @param needUrlEncode key 和 value 拼接之前是否调用 URLEncode.encode
     */
    private String spliceReqParams(char splicing, boolean needUrlEncode) {
        if (firstException != null || reqParams.size() == 0) {
            return "";
        }
        StringBuilder stringBuilder = new StringBuilder();
        try {
            for (Map.Entry<String, Object> entry : reqParams.entrySet()) {
                String k = entry.getKey();
                String v = parseValue(entry.getValue());
                if (needUrlEncode) {
                    k = URLEncoder.encode(k, charset.toString());
                    v = URLEncoder.encode(v, charset.toString());
                }
                if (stringBuilder.length() > 0) {
                    stringBuilder.append(splicing);
                }
                stringBuilder.append(k).append("=").append(v);
            }
        } catch (Exception e) {
            firstException = e;
            httpLogger.paramException(url, reqParams, charset, e);
        }
        reqParams.clear();
        return stringBuilder.toString();
    }

    /**
     * 添加请求 URL 中携带的参数，参数会调用 URLEncode.encode 然后拼接到 URL
     *
     * @param key   参数名，key 为 null 或 "" 时抛出异常
     * @param value 参数值，value 为 null 时传 ""
     */
    public final HttpBuilder addUrlParam(String key, Object value) {
        addReqParam(key, value);
        return this;
    }

    /**
     * 添加请求 URL 中携带的参数，参数会自动拼接到 URL
     *
     * @param params key 为 null 或 "" 时抛出异常；value 为 null 时传 ""；
     */
    public final HttpBuilder addUrlParam(Map<String, Object> params) {
        params.forEach(this::addReqParam);
        return this;
    }

    /**
     * 设置请求和响应的编码格式
     *
     * @param charset 默认是 Charset.defaultCharset() 一般是 UTF-8
     */
    public final HttpBuilder setCharset(Charset charset) {
        this.charset = charset;
        return this;
    }

    /**
     * 设置连接超时时间，默认 5秒
     *
     * @param seconds 连接超时时间，单位 秒
     */
    public final HttpBuilder setConnectTimeout(int seconds) {
        Assert.isTrue(seconds > 0, "连接超时时间必须大于0");
        connectionTimeout = seconds;
        return this;
    }

    /**
     * 设置读响应流超时时间，默认 5秒
     *
     * @param seconds 读响应流超时时间，单位 秒
     */
    public final HttpBuilder setReadTimeout(int seconds) {
        Assert.isTrue(seconds > 0, "读超时时间必须大于0");
        readTimeout = seconds;
        return this;
    }

    /**
     * 设置请求方法为 GET
     */
    public final HttpHeaderBuilder useGet() {
        return useHttpMethod(HttpMethod.GET);
    }

    /**
     * 设置请求方法为 POST
     */
    public final HttpHeaderBuilder usePost() {
        return useHttpMethod(HttpMethod.POST);
    }

    /**
     * 设置请求方法
     *
     * @param httpMethod 请求方法
     */
    public final HttpHeaderBuilder useHttpMethod(HttpMethod httpMethod) {
        spliceUrlAndReqParams();
        try {
            httpComponent.generateHttpHeaderBuilder(url, httpMethod, connectionTimeout, readTimeout);
        } catch (Exception e) {
            firstException = e;
            httpLogger.componentException(url, e);
        }
        return new HttpHeaderBuilder();
    }

    public class HttpHeaderBuilder {
        private HttpHeaderBuilder() {
        }

        /**
         * 检查 reqParams 中是否有参数，有的话加入 cookie
         */
        private void setCookieIfNeed() {
            if (reqParams.size() > 0) {
                setRequestHeader(HttpHeaders.COOKIE, spliceReqParams(';', false));
            }
        }

        /**
         * header 中设置 Authorization 字段
         *
         * @param authorization Authorization 的值
         */
        public final HttpHeaderBuilder setAuthorization(String authorization) {
            return setRequestHeader(HttpHeaders.AUTHORIZATION, authorization);
        }

        /**
         * header 中设置 Connection:keep-alive
         */
        public final HttpHeaderBuilder setConnectionKeepAlive() {
            return setRequestHeader(HttpHeaders.CONNECTION, "keep-alive");
        }

        /**
         * header 中设置 host
         *
         * @param host Host 的值
         */
        public final HttpHeaderBuilder setHost(String host) {
            return setRequestHeader(HttpHeaders.HOST, host);
        }

        /**
         * header 中设置 origin
         *
         * @param origin Origin 的值
         */
        public final HttpHeaderBuilder setOrigin(String origin) {
            return setRequestHeader(HttpHeaders.ORIGIN, origin);
        }

        /**
         * header 中设置 user-agent
         *
         * @param userAgent User-Agent 的值
         */
        public final HttpHeaderBuilder setUserAgent(String userAgent) {
            return setRequestHeader(HttpHeaders.USER_AGENT, userAgent);
        }

        /**
         * Cookie 中添加 key=value 对；可以添加多次；key 不能重复；
         * addCookie("JSESSIONID", "xxx") 优先级高于 setRequestHeader(HttpHeaders.COOKIE, "JSESSIONID=xxx")
         *
         * @param key   参数名，key 为 null 或 "" 时抛出异常
         * @param value 参数值，value 为 null 时传 ""
         */
        public final HttpHeaderBuilder addCookie(String key, Object value) {
            addReqParam(key, value);
            return this;
        }

        /**
         * 使用 HttpHeaders 中的静态常量做 key；该方法只作提示用；
         *
         * @param key   如 HttpHeaders.CONTENT_TYPE
         * @param value key 对应的 value
         */
        public final HttpHeaderBuilder setRequestHeader(HttpHeaders key, Object value) {
            return null;
        }

        /**
         * 设置 request header；
         * addCookie("JSESSIONID", "xxx") 优先级高于 setRequestHeader(HttpHeaders.COOKIE, "JSESSIONID=xxx")
         *
         * @param key   参数名，key 为 null 或 "" 时抛出异常
         * @param value 参数值，value 为 null 时传 ""
         */
        public final HttpHeaderBuilder setRequestHeader(String key, Object value) {
            assertKey(key);
            if (firstException == null) {
                try {
                    httpComponent.setRequestHeader(key, parseValue(value));
                } catch (Exception e) {
                    firstException = e;
                    httpLogger.componentException(url, key, parseValue(value), e);
                }
            }
            return this;
        }

        /**
         * 无需使用 requestBody 传递数据
         */
        public final HttpEmptyBodyBuilder sendEmptyData() {
            setCookieIfNeed();
            return new HttpEmptyBodyBuilder(null);
        }

        /**
         * 通过 request body 发送 byte 数组
         */
        public final HttpBytesBodyBuilder sendByteArray() {
            setCookieIfNeed();
            return new HttpBytesBodyBuilder(MediaType.APPLICATION_OCTET_STREAM);
        }

        /**
         * 通过 request body 以 text-plain 格式发送数据
         */
        public final HttpTextBodyBuilder sendTextPlain() {
            setCookieIfNeed();
            return new HttpTextBodyBuilder(MediaType.TEXT_PLAIN);
        }

        /**
         * 通过 request body 以 Content-Type:application/json 格式发送数据
         */
        public final HttpJsonBodyBuilder sendJsonData() {
            setCookieIfNeed();
            return new HttpJsonBodyBuilder(MediaType.APPLICATION_JSON);
        }

        /**
         * 通过 request body 以 Content-Type:application/x-www-form-urlencoded 格式发送数据
         */
        public final HttpFormBodyBuilder sendFormData() {
            setCookieIfNeed();
            return new HttpFormBodyBuilder(MediaType.APPLICATION_FORM_URLENCODED);
        }

        /**
         * 通过 request body 以 Content-Type:application/xml 格式发送数据
         */
        public final HttpXmlBodyBuilder sendXmlData() {
            setCookieIfNeed();
            return new HttpXmlBodyBuilder(MediaType.APPLICATION_XML);
        }

        // TODO: 20/10/15 添加新的请求体编码类型支持，理论上每一种都需要对应的 HttpXxxBodyBuilder
    }

    public class HttpEmptyBodyBuilder {
        /**
         * Http 请求 requestBody 的数据对象，传入一个完整对象
         */
        protected Object requestBodyData;

        protected HttpEmptyBodyBuilder(MediaType mediaType) {
            requestBodyData = null;
            if (mediaType != null) {
                if (firstException == null) {
                    try {
                        httpComponent.setRequestHeader(HttpHeaders.CONTENT_TYPE, mediaType.toString());
                    } catch (Exception e) {
                        firstException = e;
                        httpLogger.componentException(url, HttpHeaders.CONTENT_TYPE, mediaType.toString(), e);
                    }
                }
            }
        }

        /**
         * 统一 json 序列化配置
         *
         * @return json 序列化对象
         */
        protected final ObjectMapper serializationObjectMapper() {
            ObjectMapper mapper = new ObjectMapper();
            mapper.setVisibility(mapper.getSerializationConfig().getDefaultVisibilityChecker()
                    .withFieldVisibility(JsonAutoDetect.Visibility.ANY)
                    .withGetterVisibility(JsonAutoDetect.Visibility.NONE)
                    .withSetterVisibility(JsonAutoDetect.Visibility.NONE)
                    .withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
            return mapper;
        }

        /**
         * 将需要写入 request body 的数据转为字符串，子类按需要重写
         *
         * @return null 表示无需传输数据或数据编码异常，将不会写入 requestBody
         */
        @NonNull
        protected byte[] spliceBodyData() {
            // do nothing with empty request body
            return new byte[]{};
        }

        /**
         * 请求成功返回空的业务数据
         */
        public final <T> HttpEmptyResultBuilder<T> expectNothing() {
            return new HttpEmptyResultBuilder<>(null, spliceBodyData());
        }

        /**
         * 请求成功返回字节数组
         */
        public final <T> HttpBytesResultBuilder<byte[]> expectByteArray() {
            return new HttpBytesResultBuilder<>(byte[].class, spliceBodyData());
        }

        /**
         * 请求成功返回字符串
         */
        public final <T> HttpTextResultBuilder<String> expectTextPlain() {
            return new HttpTextResultBuilder<>(String.class, spliceBodyData());
        }

        /**
         * 请求成功返回 json 格式的数据并封装成 Map<String, String> 类型对象
         */
        public final <T> HttpJsonResultBuilder<Map<String, String>> expectJsonOfMap() {
            return new HttpJsonResultBuilder<>(new TypeReference<Map<String, String>>() {
            }, spliceBodyData());
        }

        /**
         * 请求成功返回 json 格式的数据并封装成 T 类型的对象
         *
         * @param resultType 解析成 T 类型的对象
         */
        public final <T> HttpJsonResultBuilder<T> expectJsonOfClass(Class<T> resultType) {
            return new HttpJsonResultBuilder<>(resultType, spliceBodyData());
        }

        /**
         * 请求成功返回 json 格式的数据并封装成 TypeReference 定义的泛型类型的对象
         *
         * @param resultType 如： new TypeReference<List<Obj>>() {}（无需实现方法） 将解析成 List<Obj>
         */
        public final <T> HttpJsonResultBuilder<T> expectJsonOfTypeReference(TypeReference<T> resultType) {
            return new HttpJsonResultBuilder<>(resultType, spliceBodyData());
        }
    }

    public class HttpBytesBodyBuilder extends HttpEmptyBodyBuilder {
        public HttpBytesBodyBuilder(MediaType mediaType) {
            super(mediaType);
        }

        /**
         * 添加字节数组到 request body
         *
         * @param bytes byte[]
         */
        public final HttpBytesBodyBuilder addByteArray(byte[] bytes) {
            if (requestBodyData == null) {
                requestBodyData = bytes;
            } else {
                byte[] src = (byte[]) requestBodyData;
                byte[] res = new byte[src.length + bytes.length];
                System.arraycopy(src, 0, res, 0, src.length);
                System.arraycopy(bytes, 0, res, src.length, bytes.length);
                requestBodyData = res;
            }
            return this;
        }

        @NonNull
        @Override
        protected byte[] spliceBodyData() {
            return (byte[]) requestBodyData;
        }
    }

    public class HttpTextBodyBuilder extends HttpEmptyBodyBuilder {

        protected HttpTextBodyBuilder(MediaType mediaType) {
            super(mediaType);
        }

        /**
         * 添加传输文本到 requestBody
         *
         * @param text 文本
         */
        public final HttpTextBodyBuilder addTextPlain(String text) {
            if (requestBodyData == null) {
                requestBodyData = text;
            } else {
                requestBodyData += text;
            }
            return this;
        }

        @NonNull
        @Override
        protected byte[] spliceBodyData() {
            return ((String) requestBodyData).getBytes(charset);
        }
    }

    public class HttpJsonBodyBuilder extends HttpEmptyBodyBuilder {
        private final ObjectMapper objectMapper;

        protected HttpJsonBodyBuilder(MediaType mediaType) {
            super(mediaType);
            objectMapper = serializationObjectMapper();
        }

        /**
         * 添加参数到 request body
         *
         * @param key   参数名，key 为 null 或 "" 时抛出异常
         * @param value 参数值，value 为 null 时传 null
         */
        public final HttpJsonBodyBuilder addRequestBodyParam(String key, Object value) {
            addReqParam(key, value);
            return this;
        }

        /**
         * 添加参数到 request body
         *
         * @param shallAdd 是否应该添加
         * @param key      参数名，key 为 null 或 "" 时抛出异常
         * @param value    参数值，value 为 null 时传 null
         */
        public final HttpJsonBodyBuilder addRequestBodyParam(boolean shallAdd, String key, Object value) {
            if (shallAdd) {
                addReqParam(key, value);
            }
            return this;
        }

        /**
         * 将 map 中的 key-value 添加到 request body
         *
         * @param data map foreach 进行添加
         */
        public final HttpJsonBodyBuilder addRequestBodyParam(Map<String, Object> data) {
            data.forEach(this::addRequestBodyParam);
            return this;
        }

        /**
         * 设置整个对象作为 requestBody 参数，可以和其他重载方法一起使用，但此方法仅能调用一次
         *
         * @param data 参数对象
         */
        public final HttpJsonBodyBuilder addRequestBodyParam(Object data) {
            Assert.isNull(requestBodyData, "addRequestBodyParam(Object data) 只能调用一次");
            requestBodyData = data;
            return this;
        }

        @NonNull
        @Override
        protected byte[] spliceBodyData() {
            if (firstException == null) {
                try {
                    if (requestBodyData != null && reqParams.size() > 0) {
                        Map<String, Object> map = objectMapper.convertValue(requestBodyData, new TypeReference<Map<String, Object>>() {
                        });
                        map.putAll(reqParams);
                        return objectMapper.writeValueAsString(map).getBytes(charset);
                    } else {
                        return objectMapper.writeValueAsString(requestBodyData != null ? requestBodyData : reqParams).getBytes(charset);
                    }
                } catch (Exception e) {
                    firstException = e;
                    httpLogger.paramException(url, requestBodyData, reqParams, e);
                }
            }
            return super.spliceBodyData();
        }
    }

    public class HttpFormBodyBuilder extends HttpEmptyBodyBuilder {
        protected HttpFormBodyBuilder(MediaType mediaType) {
            super(mediaType);
        }

        /**
         * 添加参数到 request body
         *
         * @param key   参数名，key 为 null 或 "" 时抛出异常
         * @param value 参数值，value 为 null 时传 null
         */
        public final HttpFormBodyBuilder addRequestBodyParam(String key, Object value) {
            addReqParam(key, value);
            return this;
        }

        /**
         * 添加参数到 request body
         *
         * @param shallAdd 是否应该添加
         * @param key      参数名，key 为 null 或 "" 时抛出异常
         * @param value    参数值，value 为 null 时传 null
         */
        public final HttpFormBodyBuilder addRequestBodyParam(boolean shallAdd, String key, Object value) {
            if (shallAdd) {
                addReqParam(key, value);
            }
            return this;
        }

        /**
         * 将 map 中的 key-value 添加到 request body
         *
         * @param data map foreach 进行添加
         */
        public final HttpFormBodyBuilder addRequestBodyParam(Map<String, Object> data) {
            data.forEach(HttpBuilder.this::addReqParam);
            return this;
        }

        /**
         * 设置整个对象作为 requestBody 参数，可以和其他重载方法一起使用，但此方法仅能调用一次
         *
         * @param data 参数对象
         */
        public final HttpFormBodyBuilder addRequestBodyParam(Object data) {
            Assert.isNull(requestBodyData, "此方法只能调用一次");
            requestBodyData = data;
            return this;
        }

        @NonNull
        @Override
        protected byte[] spliceBodyData() {
            if (firstException == null) {
                try {
                    ObjectMapper objectMapper = serializationObjectMapper();
                    if (requestBodyData != null) {
                        Map<String, Object> map = objectMapper.convertValue(requestBodyData, new TypeReference<Map<String, Object>>() {
                        });
                        reqParams.putAll(map);
                    }
                    return spliceReqParams('&', true).getBytes(charset);
                } catch (Exception e) {
                    firstException = e;
                    httpLogger.paramException(url, requestBodyData, reqParams, e);
                }
            }
            return super.spliceBodyData();
        }
    }

    public class HttpXmlBodyBuilder extends HttpEmptyBodyBuilder {
        protected HttpXmlBodyBuilder(MediaType mediaType) {
            super(mediaType);
        }

        /**
         * 设置整个对象作为 requestBody 参数，可以和其他重载方法一起使用，但此方法仅能调用一次
         *
         * @param data 参数对象
         */
        public final HttpXmlBodyBuilder addRequestBodyParam(Object data) {
            Assert.isNull(requestBodyData, "此方法只能调用一次");
            requestBodyData = data;
            return this;
        }

        @NonNull
        @Override
        protected byte[] spliceBodyData() {
            if (firstException == null) {
                try {
                    Marshaller marshaller = JAXBContext.newInstance(requestBodyData.getClass()).createMarshaller();
                    StringWriter writer = new StringWriter();
                    marshaller.marshal(requestBodyData, writer);
                    return writer.toString().getBytes(charset);
                } catch (Exception e) {
                    firstException = e;
                    httpLogger.paramException(url, requestBodyData, reqParams, e);
                }
            }
            return super.spliceBodyData();
        }
    }

    public class HttpEmptyResultBuilder<T> {
        /**
         * 预期的（请求成功的） HTTP 返回值类型
         */
        protected final Class<T> responseResultType;
        /**
         * 对 http 返回值 状态码 的监听器 map
         */
        protected final Map<HttpStatus, HttpResponseStatusListener> responseStatusListenerMap;

        protected HttpEmptyResultBuilder(Class<T> responseBodyResultType, byte[] spliceBodyData) {
            responseResultType = responseBodyResultType;
            responseStatusListenerMap = new HashMap<>();
            if (firstException == null && spliceBodyData.length > 0) {
                try {
                    httpComponent.writeRequestBody(spliceBodyData);
                } catch (Exception e) {
                    firstException = e;
                    httpLogger.componentException(url, new String(spliceBodyData, charset), charset, e);
                }
            }
        }

        /**
         * 从响应体解析对象
         */
        @NonNull
        protected HttpResult<T> parseResponseBody(InputStream inputStream) {
            return new HttpResult<>(null);
        }

        /**
         * 从响应体读取 bytes
         */
        protected final byte[] readFromInputStream(InputStream inputStream) {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            int read;
            byte[] bytes = new byte[1024];
            try {
                while ((read = inputStream.read(bytes, 0, bytes.length)) != -1) {
                    outputStream.write(bytes, 0, read);
                }
            } catch (Exception e) {
                firstException = e;
                httpLogger.paramException(url, outputStream.toByteArray(), e);
            }
            return outputStream.toByteArray();
        }

        /**
         * 添加 httpResponseStatus 自定义监听器，当 http 返回 httpStatus 状态码时执行
         *
         * @param httpStatus 状态码
         * @param listener   返回该状态码时执行的逻辑
         */
        public final HttpEmptyResultBuilder<T> addResponseStatusListener(HttpStatus httpStatus, HttpResponseStatusListener listener) {
            responseStatusListenerMap.put(httpStatus, listener);
            return this;
        }

        /**
         * 添加其他状态码监听器
         *
         * @param listener 监听器
         */
        public final HttpEmptyResultBuilder<T> addOtherResponseStatusListener(HttpResponseStatusListener listener) {
            responseStatusListenerMap.put(null, listener);
            return this;
        }

        /**
         * 构建 httpPlan 计划任务
         */
        public final HttpPlan<T> build() {
            return new HttpPlan<>(this);
        }
    }

    public class HttpBytesResultBuilder<T> extends HttpEmptyResultBuilder<T> {

        protected HttpBytesResultBuilder(Class<T> responseBodyResultType, byte[] spliceBodyData) {
            super(responseBodyResultType, spliceBodyData);
        }

        @NonNull
        @Override
        protected HttpResult<T> parseResponseBody(InputStream inputStream) {
            byte[] bytes = readFromInputStream(inputStream);
            return new HttpResult<>((T) bytes);
        }
    }

    public class HttpTextResultBuilder<T> extends HttpEmptyResultBuilder<T> {

        protected HttpTextResultBuilder(Class<T> responseBodyResultType, byte[] spliceBodyData) {
            super(responseBodyResultType, spliceBodyData);
        }

        @NonNull
        @Override
        @SuppressWarnings("unchecked")
        protected HttpResult<T> parseResponseBody(InputStream inputStream) {
            byte[] bytes = readFromInputStream(inputStream);
            try {
                return new HttpResult<>((T) new String(bytes, charset));
            } catch (Exception e) {
                firstException = e;
                httpLogger.paramException(url, bytes, charset, String.class, e);
                return new HttpResult<>(null, firstException);
            }
        }
    }

    public class HttpJsonResultBuilder<T> extends HttpEmptyResultBuilder<T> {
        /**
         * Jackson TypeReference 对象，用于支持解析 json 为泛型对象
         */
        protected final TypeReference<T> typeReference;
        protected final ObjectMapper objectMapper;

        protected HttpJsonResultBuilder(Class<T> responseBodyResultType, byte[] spliceBodyData) {
            super(responseBodyResultType, spliceBodyData);
            typeReference = null;
            objectMapper = deserializationObjectMapper();
        }

        protected HttpJsonResultBuilder(TypeReference<T> responseBodyResultType, byte[] spliceBodyData) {
            super(null, spliceBodyData);
            typeReference = responseBodyResultType;
            objectMapper = deserializationObjectMapper();
        }

        /**
         * 定义解析 ObjectMapper 配置参数
         */
        private ObjectMapper deserializationObjectMapper() {
            ObjectMapper mapper = new ObjectMapper();
            // 解析只看属性
            mapper.setVisibility(mapper.getSerializationConfig().getDefaultVisibilityChecker()
                    .withFieldVisibility(JsonAutoDetect.Visibility.ANY)
                    .withGetterVisibility(JsonAutoDetect.Visibility.NONE)
                    .withSetterVisibility(JsonAutoDetect.Visibility.NONE)
                    .withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
            // 映射对象中未定义的字段将会无视
            mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
            return mapper;
        }

        @NonNull
        @Override
        protected HttpResult<T> parseResponseBody(InputStream inputStream) {
            byte[] bytes = readFromInputStream(inputStream);
            try {
                if (typeReference != null) {
                    return new HttpResult<>(objectMapper.readValue(bytes, typeReference));
                } else {
                    return new HttpResult<>(objectMapper.readValue(bytes, responseResultType));
                }
            } catch (Exception e) {
                firstException = e;
                if (typeReference != null) {
                    httpLogger.paramException(url, bytes, charset, typeReference, e);
                } else {
                    httpLogger.paramException(url, bytes, charset, responseResultType, e);
                }
                return new HttpResult<>(null, firstException);
            }
        }
    }

    public class HttpPlan<T> {
        private final HttpEmptyResultBuilder<T> resultBuilder;

        protected HttpPlan(HttpEmptyResultBuilder<T> httpEmptyResultBuilder) {
            this.resultBuilder = httpEmptyResultBuilder;
        }

        /**
         * 执行 httpPlan
         *
         * @return 执行结果
         */
        public final HttpResult<T> execute() {
            if (firstException == null) {
                boolean responseSuccess = false;
                try {
                    HttpStatus httpStatus = httpComponent.execute();
                    if (resultBuilder.responseStatusListenerMap.containsKey(httpStatus)) {
                        resultBuilder.responseStatusListenerMap.get(httpStatus).callback(httpStatus);
                    } else if (httpStatus != null && resultBuilder.responseStatusListenerMap.containsKey(null)) {
                        resultBuilder.responseStatusListenerMap.get(null).callback(httpStatus);
                    }
                    if ((responseSuccess = (httpStatus == HttpStatus.OK))) {
                        return resultBuilder.parseResponseBody(httpComponent.getInputStream());
                    } else {
                        byte[] bytes = resultBuilder.readFromInputStream(httpComponent.getErrorStream());
                        httpLogger.httpFailed(url, httpStatus, new String(bytes, charset));
                        return new HttpResult<>(httpStatus, firstException);
                    }
                } catch (Exception e) {
                    firstException = e;
                    httpLogger.componentException(url, responseSuccess, e);
                }
            }
            return new HttpResult<>(null, firstException);
        }
    }
}
